package hit.reports;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import hit.core.HomeInventoryTracker;
import hit.core.Item;
import hit.core.ItemIndex;
import hit.core.Product;
import hit.core.ProductContainer;
import hit.core.ProductGroup;
import hit.core.ProductIndex;
import hit.core.Size;
import hit.core.StorageUnit;
import hit.core.Size.Unit;
import java.util.HashSet;

/**
 * Builds a report for the supply for a given number of Months.
 *
 */
public class NMonthSupplyReport implements ReportDesigner, Visitor {

	ItemIndex itemIndex;
	
	int months;
	Map<Product, Size> productsSize;
	
	List<ProductGroup> groupOrder;
	Map<ProductGroup, Size> groupsSize;
	Map<ProductGroup, Set<Item>>groupsItems;
	
	public NMonthSupplyReport(int months)
	{
		this.months = months;
		productsSize = new HashMap<Product, Size>();
		groupOrder = new ArrayList<ProductGroup>();
		groupsSize = new HashMap<ProductGroup, Size>();
		groupsItems = new HashMap<ProductGroup, Set<Item>>();
	}
	
	@Override
	public void VisitHITCore(HomeInventoryTracker core) {
		// Do Nothing
		itemIndex = core.GetItemIndex();
	}

	@Override
	public void VisitProductIndex(ProductIndex index) {
		// Do Nothing
	}

	@Override
	public void VisitItemIndex(ItemIndex index) {
		// Do Nothing
	}

	@Override
	public void VisitStorageUnit(StorageUnit unit) {
		// Do Nothing.
	}
	
	@Override
	public void VisitProductGroupPost(ProductGroup pg) {

		Set<Item> items = itemIndex.GetItemsByProductContainer(pg);
		if (items == null){
			items = new HashSet<Item>();
		}
		Iterator<ProductGroup> childIter = pg.GetProductGroupIterator();
		while (childIter.hasNext())
		{
			ProductGroup child = childIter.next();
			
			if (groupsItems.containsKey(child))
			{
				items.addAll(groupsItems.get(child));
			}
		}
		
		groupsItems.put(pg, items);
		
		Iterator<Item> itemIter = items.iterator();
		Size pgSize = new Size();
		pgSize.SetType(pg.GetThreeMonth().GetType());
		pgSize.SetAmount(0);
		
		while (itemIter.hasNext())
		{
			Item item = itemIter.next();
			
			if (pgSize.GetType() == Size.Unit.COUNT)
			{
				pgSize.SetAmount(pgSize.GetAmount() + 1);
			}
			else
			{
				Size convSize = item.GetProduct().GetSize().ConvertTo(pgSize.GetType());
				if (convSize != null)
				{
					pgSize.SetAmount(pgSize.GetAmount() + convSize.GetAmount());
				}
			}
		}
		
		groupsSize.put(pg, pgSize);
	}

	@Override
	public void VisitProductGroup(ProductGroup group) {
		
		groupOrder.add(group);
		Size size = new Size();
		size.SetType(group.GetThreeMonth().GetType());
	}

	@Override
	public void VisitItem(Item item) {
		
		Product prod = item.GetProduct();
		Size pSize = productsSize.get(prod);
		pSize.SetAmount(pSize.GetAmount() + 1);
	}

	@Override
	public void VisitProduct(Product product) {
		
		Size size = new Size();
		size.SetType(Unit.COUNT);
		productsSize.put(product, size);
	}

	@Override
	public void BuildReport(ReportBuilder builder) {
		
		builder.setTitle(Integer.toString(months) + "-Month Supply Report");
		
		BuildProductTable(builder);
		builder.AddObject(new BuilderBreak());
		BuildProductGroupTable(builder);
	}
	
	private void BuildProductTable(ReportBuilder builder)
	{
		DecimalFormat nf = new DecimalFormat("#.###");
		builder.AddObject(new BuilderHeader("Products"));
		
		Iterator<Product> pIter = productsSize.keySet().iterator();
		
		BuilderTable pTable = new BuilderTable();
		builder.AddObject(pTable);
		
		TableColumn pColDescrip = new TableColumn("Description");
		pTable.AddColumn(pColDescrip);
		
		TableColumn pColBarcode = new TableColumn("Barcode");
		pTable.AddColumn(pColBarcode);
		
		TableColumn pColSupply = new TableColumn(Integer.toString(months) + "-Month Supply");
		pTable.AddColumn(pColSupply);
		
		TableColumn pColCurrent = new TableColumn("Current Supply");
		pTable.AddColumn(pColCurrent);
		
		while (pIter.hasNext())
		{
			Product product = pIter.next();
			
			if (product.GetThreeMonth() <= 0)
				continue;
			
			Size totalSize = productsSize.get(product);
			
			double neededSize = product.GetThreeMonth();
			neededSize *= (double)months / 3.0;
			
			if (neededSize <= totalSize.GetAmount())
				continue;
			
			TableRow row = new TableRow();
			row.AddItem(pColDescrip, product.GetDescription());
			row.AddItem(pColBarcode, product.GetBarcode().GetValue());
			row.AddItem(pColSupply, nf.format(neededSize) + " Count");
			row.AddItem(pColCurrent, nf.format(totalSize.GetAmount()) + " Count");
			pTable.AddRow(row);
		}
	}
	
	private void BuildProductGroupTable(ReportBuilder builder)
	{
		DecimalFormat nf = new DecimalFormat("#.###");
		builder.AddObject(new BuilderHeader("Product Groups"));
		
		Iterator<ProductGroup> pgIter = groupOrder.iterator();
		
		BuilderTable pgTable = new BuilderTable();
		builder.AddObject(pgTable);
		
		TableColumn pgColName = new TableColumn("Product Group");
		pgTable.AddColumn(pgColName);
		
		TableColumn pgColStorage = new TableColumn("Storage Unit");
		pgTable.AddColumn(pgColStorage);
		
		TableColumn pgColSupply = new TableColumn(Integer.toString(months) + "-Month Supply");
		pgTable.AddColumn(pgColSupply);
		
		TableColumn pgColCurrent = new TableColumn("Current Supply");
		pgTable.AddColumn(pgColCurrent);
		
		while (pgIter.hasNext())
		{
			ProductGroup group = pgIter.next();
			
			if (group.GetThreeMonth().GetAmount() <= 0)
				continue;
			
			Size totalSize = groupsSize.get(group);
			
			double neededSize = group.GetThreeMonth().GetAmount();
			neededSize *= (double)months / 3.0;
			
			if (neededSize <= totalSize.GetAmount())
				continue;
			
			TableRow row = new TableRow();
			row.AddItem(pgColName, group.GetName());
			row.AddItem(pgColStorage, group.GetRootContainer().GetName());
			row.AddItem(pgColSupply, nf.format(neededSize) + " " + totalSize.GetType().toString());
			row.AddItem(pgColCurrent, nf.format(totalSize.GetAmount()) + 
										" " + totalSize.GetType().toString());
			pgTable.AddRow(row);
		}
	}
}
